package com.karateca.columbiaimports;

import com.karateca.columbiaimports.http.HttpDownloader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class is in charge of downloading a synchronized queue of bean with
 * multiple threads.
 * @author Andres Dominguez
 */
public class DownloadWorker implements Runnable
{
    /** Apache log */
    private static Log log = LogFactory.getLog(DownloadWorker.class);
    /** Number of threads working in this object */
    private int threadsWorking;
    /** Beans that will be processed by the threads */
    private List<ImportBean> importBeanQueue;
    /** Initialized downloader */
    private HttpDownloader downloader;
    /** Error flag */
    private boolean threadCrashed = false;
    /**Maximum numbers of threads that must be working at the same time */
    private int maxThreads;
    /** Beans that are currently being processed */
    private List<ImportBean> workingQueue;

    /**
     * Creates a new instance of DownloadWorker.
     * @param importQueue list of imports that will be processed.
     * @param httpDownloader initialized downloader.
     * @param log output log.
     */
    public DownloadWorker(List<ImportBean> importQueue, HttpDownloader httpDownloader, Log log)
    {
        this.importBeanQueue = importQueue;
        this.downloader = httpDownloader;
        this.maxThreads = 10;
        this.threadCrashed = false;
        // Create synchronized queue
        this.workingQueue = Collections.synchronizedList(new ArrayList<ImportBean>());
    }

    /**
     * Set the maximum number of simultaneous threads.
     * @param maxThreads new value.
     */
    public synchronized void setMaxThreads(int maxThreads)
    {
        this.maxThreads = maxThreads;
    }

    /**
     * Increase thread count.
     */
    private synchronized void incThreadsWorking()
    {
        this.threadsWorking++;
    }

    /**
     * Decrease thread count.
     */
    private synchronized void decThreadsWorking()
    {
        this.threadsWorking--;
    }

    /**
     * Getter for property threadsWorking.
     * @return number of threads currently working.
     */
    public synchronized int getThreadsWorking()
    {
        return this.threadsWorking;
    }

    /**
     * Start downloading data. The thread will consume all the beans in the
     * queue and update the thread count.
     */
    private void startProcessing()
    {
        ImportBean bean = null;
        incThreadsWorking();

        while ((this.importBeanQueue.size() > 0) && (this.threadsWorking <= this.maxThreads))
        {
            try
            {
                // Get next bean to be processed
                bean = this.importBeanQueue.remove(0);
                this.workingQueue.add(bean);
                processBean(bean);
                this.workingQueue.remove(bean);
            }
            catch (InterruptedException ie)
            {
                // Return bean to queue and exit method
                log.error("Catched interrupted exception, returning bean to queue", ie);
                this.workingQueue.remove(bean);
                this.importBeanQueue.add(0, bean);
                decThreadsWorking();
                return;
            }
            catch (Exception e)
            {
                // Not an error type 3, break loop
                this.threadCrashed = true;
                log.error("Error in link, Thread is dying " + bean.getLink(), e);
                break;
            }
        }
        decThreadsWorking();
    }

    /**
     * Download all the data for an import bean. The data inclodes the series
     * and the DUA type B
     * @param bean import bean containing the link. The data will be added to this
     * object.
     * @throws java.lang.Exception in case of error.
     */
    public void processBean(ImportBean bean) throws Exception
    {
        String originalDua;
        String duaB;
        String duaReportLink;
        String duaBLink;
        String duaReport;
        String formatBIntermidiatePage;
        List<Map<String, String>> duaReportValues;

        try
        {
            // Download DUA
            log.debug("Downloading original DUA");
            originalDua = this.downloader.doGet(bean.getLink());
            duaReportLink = ImportDownloader.findDuaReportLink(originalDua);
            duaBLink = ImportDownloader.findDuaBLink(originalDua);

            // Get DUA report and parse it
            log.debug("Downloading report");
            duaReport = this.downloader.doGet(duaReportLink);
            duaReportValues = ImportDownloader.parseDuaReport(duaReport, bean.getLink());
            bean.setDuaHeader(duaReportValues.remove(0));
            bean.setSeries(duaReportValues);

            // Get link to download DUA B report
            log.debug("Downloading DUA B");

            // DUA B is only available on years 98 and later
            if(isLinkAfter97(bean.getLink()) == false)
            {
                bean.setDuaB(new EmptyMap(ImportDownloader.NULL_VALUE));
                return;
            }

            formatBIntermidiatePage = this.downloader.doGet(duaBLink);
            // Check if format B is present
            if (formatBIntermidiatePage.contains("DECLARACION DE IMPORTACION NO TIENE FORMATO B") ||
                    formatBIntermidiatePage.contains("DECLARACION DE IMPORTACION  NO TIENE FORMATO B") ||
                    formatBIntermidiatePage.contains("Ha ocurrido un error tipo 2. Favor intentar en breve"))
            {
                log.debug("DUA has no format B");
                // Insert empty map
                bean.setDuaB(new EmptyMap("null"));
            }
            else
            {
                // Download DUA B and parse it
                duaBLink = ImportDownloader.findDuaBFinalLink(formatBIntermidiatePage);
                // Check if DUA B is present
                duaB = this.downloader.doGet(duaBLink);
                if (duaB.contains("No se encontro datos con esa informaci"))
                {
                    log.warn("DUA has no format B");
                    bean.setDuaB(new EmptyMap("null"));
                }
                else
                {
                    bean.setDuaB(ImportDownloader.parseDuaReportB(duaB, duaBLink));
                }
            }
        }
        catch (Exception e)
        {
            String errorMsg = e.getMessage();
            if (errorMsg.contains("Ha ocurrido un error tipo 3. Favor intentar en breve") ||
                    errorMsg.contains("Server returned HTTP response code: 500") ||
                    errorMsg.contains("n Definitiva no existe..."))
            {
                // Fill bean with null values
                log.error("Bean has error type 3 ignoring [" + bean.getLink() + "]", e);
                Map nullMap = new EmptyMap("null");
                // Set empty header if not set
                if (bean.getDuaHeader() == null)
                {
                    bean.setDuaHeader(nullMap);
                }
                // Set empty series if not set
                if (bean.getSeries() == null)
                {
                    bean.setSeries(new ArrayList<Map<String, String>>());
                    bean.getSeries().add(nullMap);
                }
                // Set empty DUA B if not set
                if (bean.getDuaB() == null)
                {
                    bean.setDuaB(nullMap);
                }
            }
            else
            {
                log.error("Error reading DUA B [" + bean.getLink() + "]", e);
                throw e;
            }

        }
    }

    /**
     * Determine if the year of the import is after 1997 (Jan 1st and after).
     * @param link import link.
     * @return true if the year of the import is 1998 or after.
     * @throws java.lang.Exception when year cannot be parsed
     */
    public static boolean isLinkAfter97(String link) throws Exception
    {
        int year;
        int index;
        String yearString;
        boolean result = false;

        try
        {
            // Parse year from link. Pattern looks like this:
            // http://www.aduanet.gob.pe/servlet/SgCDUI2?codaduana=118&numecorre=081869&anoprese=1997&option=una&n=10
            index = link.indexOf("anoprese");
            yearString = link.substring(link.indexOf("anoprese"));
            yearString = link.substring(index + 9, index + 13);
            year = Integer.parseInt(yearString);
            result = (year > 1997);
        }
        catch (Exception e)
        {
            log.error("Error parsing year from link [" + link + "]");
            throw e;
        }

        return result;
    }

    /**
     * Entry point for newly created threads.
     */
    public void run()
    {
        startProcessing();
    }

    /**
     * @return the threadCrashed
     */
    public boolean isThreadCrashed()
    {
        return threadCrashed;
    }

    /**
     * @return the workingQueue
     */
    public List<ImportBean> getWorkingQueue()
    {
        return workingQueue;
    }
}
